﻿#region Using References

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

#endregion

namespace Beaker.OpenCube.Game
{
	public sealed class GeneratorOptions
		: ObservableExpandoObject
	{
		public GeneratorOptions( IEnumerable<GeneratorLayerOptions> layers, BiomeType biome, GeneratorStructureOptions options )
		{
			Contract.Requires<ArgumentOutOfRangeException>( Enum.IsDefined( typeof( BiomeType ), biome ) );

			_layers = new ObservableCollection<GeneratorLayerOptions>( layers ?? new GeneratorLayerOptions[0] );
			_biome = biome;
			_structureOptions = options ?? new GeneratorStructureOptions();
		}

		/// <summary>
		/// Gets the version of the generator options.
		/// </summary>
		/// <remarks>
		/// The version is currently always 2. Older (1) is not supported. Future version will be added as they come out.;
		/// </remarks>
		public int Version { get { return 2; } }

		private readonly ObservableCollection<GeneratorLayerOptions> _layers;

		/// <summary>
		/// Gets the collection of layers.
		/// </summary>
		/// <value>
		/// The collection of layers.
		/// </value>
		public ObservableCollection<GeneratorLayerOptions> Layers
		{
			get { return _layers; }
		}

		private BiomeType _biome;

		/// <summary>
		/// Gets or sets the biome.
		/// </summary>
		/// <value>
		/// The biome.
		/// </value>
		public BiomeType Biome
		{
			get { return _biome; }
			set { SetValue( ref _biome, value ); }
		}

		private readonly GeneratorStructureOptions _structureOptions;

		/// <summary>
		/// Gets the structure options.
		/// </summary>
		/// <value>
		/// The structure options.
		/// </value>
		public GeneratorStructureOptions StructureOptions
		{
			get { return _structureOptions; }
		}

		public override string ToString()
		{
			// for now we always render version 2 of the options string
			var sb = new StringBuilder( "2;" );

			foreach( GeneratorLayerOptions layer in Layers )
			{
				if( sb.Length > 2 )
					sb.Append( ',' );
				sb.Append( layer );
			}
			sb.Append( ';' ).Append( (int)Biome );

			if( StructureOptions != null && StructureOptions.Any() )
				sb.Append( ';' ).Append( StructureOptions );

			return sb.ToString();
		}


		/// <summary>
		/// Tries to parse the given string as Generator Options
		/// </summary>
		/// <param name="settingsString">The settings string to parse.</param>
		/// <param name="result">The result of the parse if the return value is <c>true</c>.</param>
		/// <returns><c>true</c> if parse succeeded, otherwise <c>false</c>.</returns>
		public static bool TryParse( string settingsString, out GeneratorOptions result )
		{
			Contract.Ensures( !Contract.Result<bool>() || Contract.ValueAtReturn( out result ) != null );
			result = null;

			if( !string.IsNullOrEmpty( settingsString ) )
			{
				// Split between the ; signs
				string[] parts = settingsString.Split( ';' ).Select( s => s.Trim() ).ToArray();
				if( parts.Length < 3 || parts.Length > 4 )
					return false;

				// Part 1 is verison number
				int versionNumber;
				if( !int.TryParse( parts[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out versionNumber ) )
					return false;
				if( versionNumber != 2 ) // we only support the current version 2, for now.
					return false;

				// Part 2 are layers
				GeneratorLayerOptions[] layers;
				if( !TryParseLayers( parts[1], out layers ) )
					return false;

				// Part 3 is the biome
				int biomeId;
				if( !int.TryParse( parts[2], NumberStyles.Integer, CultureInfo.InvariantCulture, out biomeId ) )
					return false;
				var biomeType = (BiomeType)Enum.ToObject( typeof( BiomeType ), biomeId );
				if( !Enum.IsDefined( typeof( BiomeType ), biomeType ) )
					return false;

				// Part 4, structure options
				GeneratorStructureOptions options = null;
				if( parts.Length == 4 )
				{

					if( !GeneratorStructureOptions.TryParse( parts[3], out options ) )
						return false;
				}

				// create options object
				result = new GeneratorOptions( layers, (BiomeType)biomeId, options );
				return true;
			}

			return false;
		}

		private static bool TryParseLayers( string layerConfigString, out GeneratorLayerOptions[] layers )
		{
			layers = null;

			// the block layer setting are seperated by comma signs
			string[] layerConfigStrings = layerConfigString.Split( ',' );

			var layerList = new List<GeneratorLayerOptions>();
			foreach( string layerConfig in layerConfigStrings )
			{
				GeneratorLayerOptions layer;
				if( !GeneratorLayerOptions.TryParse( layerConfig, out layer ) )
					return false;

				layerList.Add( layer );
			}

			layers = layerList.ToArray();
			return true;
		}
	}

	public sealed class GeneratorLayerOptions
		: IEquatable<GeneratorLayerOptions>
	{
		public GeneratorLayerOptions( BlockType blockType )
			: this( 1, blockType, Nibble.Zero )
		{
		}

		public GeneratorLayerOptions( BlockType blockType, Nibble blockData )
			: this( 1, blockType, blockData )
		{
		}

		public GeneratorLayerOptions( int repeatCount, BlockType blockType )
			: this( repeatCount, blockType, Nibble.Zero )
		{
			Contract.Requires<ArgumentOutOfRangeException>( repeatCount > 0 );
		}

		public GeneratorLayerOptions( int repeatCount, BlockType blockType, Nibble blockData )
		{
			Contract.Requires<ArgumentOutOfRangeException>( repeatCount > 0, "At least one block is required" );
			Contract.Requires<ArgumentOutOfRangeException>( Enum.IsDefined( typeof( BlockType ), blockType ), "Should be a valid block" );

			_repeatCount = repeatCount;
			_blockType = blockType;
			_blockData = blockData;
		}

		[ContractInvariantMethod]
		private void ContactInvariant()
		{
			Contract.Invariant( _repeatCount > 0 );
			Contract.Invariant( Enum.IsDefined( typeof( BlockType ), _blockType ) );
		}

		private readonly int _repeatCount;

		/// <summary>
		/// Gets the repeat count.
		/// </summary>
		/// <value>
		/// The repeat count.
		/// </value>
		[Pure]
		public int RepeatCount
		{
			get { return _repeatCount; }
		}

		private readonly BlockType _blockType;

		/// <summary>
		/// Gets the type of the block.
		/// </summary>
		/// <value>
		/// The type of the block.
		/// </value>
		[Pure]
		public BlockType BlockType
		{
			get { return _blockType; }
		}

		private readonly Nibble _blockData;

		/// <summary>
		/// Gets the data associated with the block.
		/// </summary>
		/// <value>
		/// The data associated with the block.
		/// </value>
		[Pure]
		public Nibble BlockData
		{
			get { return _blockData; }
		}

		/// <summary>
		/// Determines whether the specified <see cref="System.Object" /> is equal to this instance.
		/// </summary>
		/// <param name="obj">The <see cref="System.Object" /> to compare with this instance.</param>
		/// <returns>
		///   <c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.
		/// </returns>
		public override bool Equals( object obj )
		{
			if( ReferenceEquals( null, obj ) ) return false;
			if( ReferenceEquals( this, obj ) ) return true;
			return obj is GeneratorLayerOptions && Equals( (GeneratorLayerOptions)obj );
		}

		/// <summary>
		/// Indicates whether the current object is equal to another object of the same type.
		/// </summary>
		/// <returns>
		/// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
		/// </returns>
		/// <param name="other">An object to compare with this object.</param>
		public bool Equals( GeneratorLayerOptions other )
		{
			if( ReferenceEquals( null, other ) ) return false;
			if( ReferenceEquals( this, other ) ) return true;
			return _repeatCount == other._repeatCount && _blockType.Equals( other._blockType ) && _blockData.Equals( other._blockData );
		}

		/// <summary>
		/// Returns a hash code for this instance.
		/// </summary>
		/// <returns>
		/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. 
		/// </returns>
		[Pure]
		public override int GetHashCode()
		{
			unchecked
			{
				return 0x6a42bb38 ^ ( _repeatCount * 397 ) ^ _blockType.GetHashCode();
			}
		}

		[Pure]
		public override string ToString()
		{
			var sb = new StringBuilder();

			// more as one repeat? prepand block by mutliplication
			if( _repeatCount > 1 )
				sb.Append( _repeatCount ).Append( 'x' );

			// add block id
			sb.Append( (int)_blockType );

			// block data? then append the block data number after the block id
			if( _blockData > 0 )
				sb.Append( ':' ).Append( _blockData.Value );

			return sb.ToString();
		}

		public static bool TryParse( string layerOptionString, out GeneratorLayerOptions layerOptions )
		{
			layerOptions = null;

			// find a x sign
			int xIndex = layerOptionString.IndexOf( 'x' );

			if( xIndex != -1 )
			{
				// x sign, so parse part before the x as the number of repeats
				int count;
				if( !int.TryParse( layerOptionString.Substring( 0, xIndex ), NumberStyles.Integer, CultureInfo.InvariantCulture, out count ) )
					return false;
				if( count < 1 )
					return false;

				// the part after the x is the block data
				BlockType blockType;
				Nibble blockData;

				if( !TryParseBlockInfo( layerOptionString.Substring( xIndex + 1 ), out blockType, out blockData ) )
					return false;

				layerOptions = new GeneratorLayerOptions( count, blockType, blockData );
				return true;
			}
			else
			{
				// no x sign, so the whole string can be parsed as block data
				BlockType blockType;
				Nibble blockData;

				if( !TryParseBlockInfo( layerOptionString, out blockType, out blockData ) )
					return false;

				layerOptions = new GeneratorLayerOptions( blockType, blockData );
				return true;
			}
		}

		/// <summary>
		/// Tries to parse the block information
		/// </summary>
		/// <param name="blockInfoString">The block info string that should be parsed.</param>
		/// <param name="blockType">Type of the block as given in the string to parse.</param>
		/// <param name="blockData">The block data as given in the string to parse, or 0.</param>
		/// <returns></returns>
		private static bool TryParseBlockInfo( string blockInfoString, out BlockType blockType, out Nibble blockData )
		{
			blockType = BlockType.Air;
			blockData = Nibble.Zero;

			int colonIndex = blockInfoString.IndexOf( ':' );

			// when no colon sign, only block id available
			if( colonIndex < 0 )
			{
				// parse whole string as a block id
				int blockId;
				if( !int.TryParse( blockInfoString, NumberStyles.Integer, CultureInfo.InvariantCulture, out blockId ) )
					return false;
				blockType = (BlockType)Enum.ToObject( typeof( BlockType ), blockId );
				if( !Enum.IsDefined( typeof( BlockType ), blockType ) )
					return false;

				return true;
			}
			else
			{
				// parse part before : as a block id
				int blockId;
				if( !int.TryParse( blockInfoString.Substring( 0, colonIndex ), NumberStyles.Integer, CultureInfo.InvariantCulture, out blockId ) )
					return false;
				blockType = (BlockType)Enum.ToObject( typeof( BlockType ), blockId );
				if( !Enum.IsDefined( typeof( BlockType ), blockType ) )
					return false;

				// parse part after : as block data (like wool color, or snow thickness etc.)
				int blockDataValue;
				if( !int.TryParse( blockInfoString.Substring( colonIndex + 1 ), NumberStyles.Integer, CultureInfo.InvariantCulture, out blockDataValue ) )
					return false;
				if( blockDataValue < 0 || blockDataValue > 15 )
					return false;

				blockData = (Nibble)blockDataValue;
				return true;
			}
		}
	}

	public sealed class GeneratorStructureOptions
		: ObservableCollection<GeneratorStructureOption>
	{
		public GeneratorStructureOptions()
		{
		}

		public GeneratorStructureOptions( IEnumerable<GeneratorStructureOption> options )
			: base( options )
		{
		}

		/// <summary>
		/// Gets if there are any structure options
		/// </summary>
		/// <returns><c>true</c> when there are structure options, otherwise <c>false</c>.</returns>
		public bool Any()
		{
			return Count > 0;
		}

		public override string ToString()
		{
			var sb = new StringBuilder();

			foreach( GeneratorStructureOption structureOption in this )
			{
				if( sb.Length > 0 )
					sb.Append( ',' );
				sb.Append( structureOption );
			}

			return sb.ToString();
		}

		public static bool TryParse( string generatorStructureOptions, out GeneratorStructureOptions options )
		{
			options = null;

			if( string.IsNullOrWhiteSpace( generatorStructureOptions ) )
				return true;

			// split on comma's and parse each
			string[] structureOptionStrings = generatorStructureOptions.Split( ',' );
			var optionList = new List<GeneratorStructureOption>();
			foreach( string structureOptionString in structureOptionStrings )
			{
				GeneratorStructureOption option;
				if( !GeneratorStructureOption.TryParse( structureOptionString, out option ) )
					return false;
				optionList.Add( option );
			}

			options = new GeneratorStructureOptions( optionList );
			return true;
		}
	}

	public sealed class GeneratorStructureOption
		: ObservableExpandoObject, IEquatable<GeneratorStructureOption>
	{
		public GeneratorStructureOption( string name )
			: this( name, null )
		{
			Contract.Requires<ArgumentNullException>( !string.IsNullOrEmpty( name ) );
		}

		public GeneratorStructureOption( string name, IDictionary<string, int> values )
		{
			Contract.Requires<ArgumentNullException>( !string.IsNullOrEmpty( name ) );
			Contract.Ensures( _name == name );

			// create name
			_name = name;

			// copy values to expando parameters
			if( values != null )
			{
				foreach( KeyValuePair<string, int> kv in values )
					SetValue( kv.Value, kv.Key );
			}
		}

		private readonly string _name;

		/// <summary>
		/// Gets the name of this structure option.
		/// </summary>
		/// <value>
		/// The name of the structure option.
		/// </value>
		public string Name
		{
			get
			{
				Contract.Ensures( Contract.Result<string>() == _name );
				Contract.Ensures( !string.IsNullOrEmpty( Contract.Result<string>() ) );

				return _name;
			}
		}

		public override bool Equals( object obj )
		{
			if( obj is GeneratorStructureOption )
				return Equals( (GeneratorStructureOption)obj );

			return base.Equals( obj );
		}

		public bool Equals( GeneratorStructureOption other )
		{
			if( ReferenceEquals( null, other ) ) return false;
			if( ReferenceEquals( this, other ) ) return true;

			if( !string.Equals( _name, other._name ) )
				return false;

			if( ExpandoProperties == null && other.ExpandoProperties == null )
				return true;
			if( ExpandoProperties == null || other.ExpandoProperties == null )
				return false;
			if( ExpandoProperties.Count != other.ExpandoProperties.Count )
				return false;

			foreach( KeyValuePair<string, object> property in ExpandoProperties )
			{
				if( !other.ExpandoProperties.ContainsKey( property.Key ) )
					return false;

				if( !Equals( property.Value, other.GetValue( property.Key ) ) )
					return false;
			}

			return true;
		}

		public override int GetHashCode()
		{
			return 0x16a07f43 ^ _name.GetHashCode();
		}

		public override string ToString()
		{
			var sb = new StringBuilder();

			sb.Append( _name );

			if( ExpandoProperties != null && ExpandoProperties.Count > 0 )
			{
				sb.Append( '(' );

				bool isFirst = true;
				foreach( KeyValuePair<string, object> property in ExpandoProperties )
				{
					if( !isFirst )
						sb.Append( ' ' );
					else
						isFirst = false;
					sb.Append( property.Key ).Append( '=' ).Append( property.Value );

				}

				sb.Append( ')' );
			}

			return sb.ToString();
		}


		private static readonly Regex Regex = new Regex( @"(?<name>[^\s\(]+)(?:\s*\(\s*(?<valName>[^\s=]+)\s*=\s*(?<val>\d+)(?:\s+(?<valName>[^\s=]+)\s*=\s*(?<val>\d+))*\s*\))?", RegexOptions.CultureInvariant | RegexOptions.Compiled );

		public static bool TryParse( string structureOptionString, out GeneratorStructureOption option )
		{
			option = null;

			Match m = Regex.Match( structureOptionString );
			if( !m.Success )
				return false;

			string name = m.Groups["name"].Value;
			var values = new Dictionary<string, int>();

			if( m.Groups["valName"].Captures.Count > 0 )
			{
				for( int i = 0; i < m.Groups["valName"].Captures.Count; i++ )
				{
					values.Add(
						m.Groups["valName"].Captures[i].Value,
						int.Parse( m.Groups["val"].Captures[i].Value )
					);
				}
			}

			option = new GeneratorStructureOption( name, values );
			return true;
		}
	}
}
